package com.gitee.melin.bee.util;

import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerState;
import io.fabric8.kubernetes.api.model.ContainerStateTerminated;
import io.fabric8.kubernetes.api.model.ContainerStateWaiting;
import io.fabric8.kubernetes.api.model.ContainerStatus;
import io.fabric8.kubernetes.api.model.Node;
import io.fabric8.kubernetes.api.model.NodeAddress;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodCondition;
import io.fabric8.kubernetes.api.model.PodStatus;
import io.fabric8.kubernetes.api.model.Quantity;
import io.fabric8.kubernetes.api.model.ResourceRequirements;
import io.fabric8.kubernetes.api.model.metrics.v1beta1.ContainerMetrics;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.readiness.Readiness;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

public class KubernetesUtils {

    private static final String POD_RUNNING = "Running";

    private static final String POD_INITIALIZING = "PodInitializing";

    private static final String CONTAINER_COMPLETED = "Completed";

    private static final long GIGABYTE = 1024L * 1024L * 1024L;

    private KubernetesUtils() {}

    public static NodeMetrics getNodeMetrics(KubernetesClient kubernetesClient, String nodeName) {
        Node specificNode = kubernetesClient.nodes().withName(nodeName).get();
        Map<String, Quantity> capacity = specificNode.getStatus().getCapacity();
        int cpuCapacity = Quantity.fromNumericalAmount(capacity.get("cpu").getNumericalAmount(), "m")
                .getNumericalAmount()
                .intValue();
        long memoryCapacity = Quantity.fromNumericalAmount(
                        capacity.get("memory").getNumericalAmount(), "Mi")
                .getNumericalAmount()
                .intValue();

        Map<String, Quantity> allocatable = specificNode.getStatus().getAllocatable();
        int cpuAllocatable = Quantity.fromNumericalAmount(allocatable.get("cpu").getNumericalAmount(), "m")
                .getNumericalAmount()
                .intValue();
        long memoryAllocatable = Quantity.fromNumericalAmount(
                        allocatable.get("memory").getNumericalAmount(), "Mi")
                .getNumericalAmount()
                .intValue();
        return new NodeMetrics(cpuCapacity, cpuAllocatable, memoryCapacity, memoryAllocatable);
    }

    public static PodMetrics getPodMetrics(KubernetesClient kubernetesClient, Pod pod) {
        Integer cpuLimit = 0, cpuRequest = 0, cpuUsage = 0;
        Long memLimit = 0L, memRequest = 0L, memUsage = 0L;

        io.fabric8.kubernetes.api.model.metrics.v1beta1.PodMetrics podMetrics = kubernetesClient
                .top()
                .pods()
                .withName(pod.getMetadata().getName())
                .metric();
        for (ContainerMetrics containerMetrics : podMetrics.getContainers()) {
            Map<String, Quantity> usage = containerMetrics.getUsage();
            cpuUsage += usage.get("cpu").getNumericalAmount().intValue();
            memUsage += usage.get("memory").getNumericalAmount().intValue();
        }

        for (Container container : pod.getSpec().getContainers()) {
            ResourceRequirements resources = container.getResources();
            resources.getLimits().get("cpu");

            if (resources.getLimits().get("cpu") != null) {
                cpuLimit +=
                        resources.getLimits().get("cpu").getNumericalAmount().intValue();
            }
            if (resources.getLimits().get("memory") != null) {
                memLimit +=
                        resources.getLimits().get("memory").getNumericalAmount().intValue();
            }

            if (resources.getRequests().get("cpu") != null) {
                cpuLimit +=
                        resources.getRequests().get("cpu").getNumericalAmount().intValue();
            }
            if (resources.getRequests().get("memory") != null) {
                memLimit += resources
                        .getRequests()
                        .get("memory")
                        .getNumericalAmount()
                        .intValue();
            }
        }

        return new PodMetrics(cpuUsage, cpuLimit, cpuRequest, memUsage, memLimit, memRequest);
    }

    public static Optional<String> getMasterHost(KubernetesClient kubernetesClient) {
        return kubernetesClient.nodes().list().getItems().stream()
                .filter(node -> node.getSpec().getUnschedulable() == null
                        || !node.getSpec().getUnschedulable())
                .flatMap(node -> node.getStatus().getAddresses().stream())
                .filter(nodeAddress -> "InternalIP".equals(nodeAddress.getType()))
                .map(NodeAddress::getAddress)
                .filter(ip -> !ip.isEmpty())
                .findAny();
    }

    private static String formatToGi(long bytes) {
        // 将字节数除以 GIGABYTE，并强制转换为 double 类型以进行浮点数运算
        double gigabytes = (double) bytes / GIGABYTE;

        // 使用 String.format 方法格式化结果，保留两位小数
        // "%.2f" 表示格式化为浮点数，并保留小数点后两位
        return String.format("%.2f", gigabytes);
    }

    /**
     * Returns {@code true} if the given pod is running. Returns {@code false} otherwise.
     *
     * <p>The definition when a pod is considered running can be found at
     * <a href=
     * "https://github.com/openshift/origin/blob/master/vendor/k8s.io/api/core/v1/types.go#L3564">k8s.io/api/core/v1/types.go#L3564</a>
     * It states:
     * "PodRunning means the pod has been bound to a node and all of the containers have been started.
     * At least one container is still running or is in the process of being restarted."
     *
     * <p>The logic is taken from <a href=
     * "https://github.com/openshift/origin/blob/master/vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers.go#L695-L781">(kubernetes/printers.go)
     * printPod()</a>
     * and <a href=
     * "https://github.com/openshift/origin-web-console/blob/master/app/scripts/filters/resources.js#L1012-L1088">(openshift-web-console/resources.js)
     * podStatus()</a>
     *
     * @param pod the pod to return the running status for
     * @return returns true if the pod is running
     */
    public static boolean isRunning(Pod pod) {
        if (isInStatus(POD_RUNNING, pod)) {
            return true;
        }
        if (hasDeletionTimestamp(pod) || isInitializing(pod)) {
            return false;
        }
        if (hasRunningContainer(pod)) {
            return !hasCompletedContainer(pod) || Readiness.isPodReady(pod);
        }
        return false;
    }

    /**
     * ref: https://github.com/openshift/origin/blob/b63b14412774dc3db4ce625b54804bd3169a2b4d/vendor/k8s.io/kubernetes/pkg/printers/internalversion/printers.go#L881
     * https://foxutech.com/how-to-troubleshoot-kubernetes-pod-in-pending-state/
     * @param pod
     * @return pod status
     */
    public static PodInfo getPodInfo(Pod pod) {
        PodStatus podStatus = pod.getStatus();
        String podPhase = podStatus.getPhase();
        String status = podPhase;
        int totalContainers = pod.getSpec().getContainers().size();
        int readyContainers = 0;

        if (StringUtils.isNotBlank(podStatus.getReason())) {
            status = podStatus.getReason();
        }

        List<PodCondition> conditions = podStatus.getConditions();
        for (PodCondition condition : conditions) {
            if ("PodScheduled".equals(condition.getType())
                    && "PodReasonSchedulingGated".equals(condition.getReason())) {
                status = "PodReasonSchedulingGated";
            }
        }

        Map<String, Container> initContainers = new HashMap<>();
        for (Container container : pod.getSpec().getInitContainers()) {
            initContainers.put(container.getName(), container);
            if (isRestartableInitContainer(container)) {
                totalContainers++;
            }
        }

        List<ContainerStatus> containerStatuses = podStatus.getInitContainerStatuses();
        int index = 0;
        boolean initializing = false;
        for (ContainerStatus container : containerStatuses) {
            ContainerStateTerminated terminated = container.getState().getTerminated();
            ContainerStateWaiting waiting = container.getState().getWaiting();

            if (terminated != null && terminated.getExitCode() == 0) {
                //
            } else if (isRestartableInitContainer(initContainers.get(container.getName()))
                    && container.getStarted() != null
                    && container.getStarted()) {

                if (container.getReady()) {
                    readyContainers++;
                }
                //
            } else if (terminated != null) {
                if (StringUtils.isBlank(terminated.getReason())) {
                    Integer signal = terminated.getSignal();
                    if (signal != 0) {
                        status = "Init:Signal:" + terminated.getSignal();
                    } else {
                        status = "Init:ExitCode:" + terminated.getExitCode();
                    }
                } else {
                    status = "Init:" + terminated.getReason();
                }

                initializing = true;
            } else if (waiting != null
                    && StringUtils.isNotBlank(waiting.getReason())
                    && !"PodInitializing".equals(waiting.getReason())) {
                status = "Init:" + waiting.getReason();
                initializing = true;
            } else {
                status = "Init:" + index + "/"
                        + pod.getSpec().getInitContainers().size();
                initializing = true;
            }

            index++;
        }

        if (!initializing || isPodInitializedConditionTrue(podStatus)) {
            List<ContainerStatus> containerStatusList = podStatus.getContainerStatuses();

            boolean hasRunning = false;
            for (int i = containerStatusList.size() - 1; i >= 0; i--) {
                ContainerStatus container = containerStatusList.get(i);
                ContainerState state = container.getState();

                if (state.getWaiting() != null
                        && StringUtils.isNotBlank(state.getWaiting().getReason())) {
                    status = state.getWaiting().getReason();
                } else if (state.getTerminated() != null
                        && StringUtils.isNotBlank(state.getTerminated().getReason())) {
                    status = state.getTerminated().getReason();
                } else if (state.getTerminated() != null
                        && StringUtils.isBlank(state.getTerminated().getReason())) {
                    if (state.getTerminated().getSignal() != 0) {
                        status = "Signal:" + state.getTerminated().getSignal();
                    } else {
                        status = "ExitCode:" + state.getTerminated().getExitCode();
                    }
                } else if (container.getReady() && state.getRunning() != null) {
                    hasRunning = true;
                    readyContainers++;
                }
            }

            // change pod status back to "Running" if there is at least one container still reporting as "Running"
            // status
            if ("Completed".equals(status) && hasRunning) {
                if (hasPodReadyCondition(podStatus.getConditions())) {
                    status = "Running";
                } else {
                    status = "NotReady";
                }
            }
        }

        if (pod.getMetadata().getDeletionTimestamp() != null
                && "NodeUnreachablePodReason".equals(podStatus.getReason())) {
            status = "Unknown";
        } else if (pod.getMetadata().getDeletionTimestamp() != null && !isPodPhaseTerminal(podPhase)) {
            status = "Terminating";
        }

        return new PodInfo(pod.getMetadata().getName(), status, readyContainers, totalContainers);
    }

    private static boolean isRestartableInitContainer(Container initContainer) {
        if (initContainer == null || initContainer.getRestartPolicy() == null) {
            return false;
        }

        return "ContainerRestartPolicyAlways".equals(initContainer.getRestartPolicy());
    }

    private static boolean isPodInitializedConditionTrue(PodStatus podStatus) {
        for (PodCondition condition : podStatus.getConditions()) {
            if ("PodInitialized".equals(condition.getType())) {
                continue;
            }

            return "ConditionTrue".contains(condition.getStatus());
        }

        return false;
    }

    private static boolean hasPodReadyCondition(List<PodCondition> conditions) {
        for (PodCondition condition : conditions) {
            if ("PodReady".equals(condition.getType()) && "ConditionTrue".equals(condition.getStatus())) {
                return true;
            }
        }
        return false;
    }

    private static boolean isPodPhaseTerminal(String phase) {
        return "PodFailed".equals(phase) || "PodSucceeded".equals(phase);
    }

    private static boolean isInStatus(String expected, Pod pod) {
        if (pod == null || pod.getStatus() == null || expected == null) {
            return false;
        }

        return expected.equals(pod.getStatus().getPhase())
                || expected.equals(pod.getStatus().getReason());
    }

    private static boolean hasDeletionTimestamp(Pod pod) {
        if (pod == null) {
            return false;
        }
        return pod.getMetadata() != null && pod.getMetadata().getDeletionTimestamp() != null;
    }

    /**
     * Returns {@code true} if the given pod has at least 1 container that's initializing.
     *
     * @param pod the pod to return the initializing status for
     * @return returns true if the pod is initializing
     */
    public static boolean isInitializing(Pod pod) {
        if (pod == null) {
            return false;
        }
        return pod.getStatus().getInitContainerStatuses().stream().anyMatch(KubernetesUtils::isInitializing);
    }

    /**
     * Returns {@code true} if the given container status is terminated with an non-0 exit code or is waiting.
     * Returns {@code false} otherwise.
     */
    private static boolean isInitializing(ContainerStatus status) {
        if (status == null) {
            return true;
        }

        ContainerState state = status.getState();
        if (state == null) {
            return true;
        }
        if (isTerminated(state)) {
            return hasNonNullExitCode(state);
        } else if (isWaiting(state)) {
            return !isWaitingInitializing(state);
        } else {
            return true;
        }
    }

    private static boolean isTerminated(ContainerState state) {
        return state == null || state.getTerminated() != null;
    }

    private static boolean hasNonNullExitCode(ContainerState state) {
        return state.getTerminated() != null && state.getTerminated().getExitCode() != 0;
    }

    private static boolean isWaiting(ContainerState state) {
        return state == null || state.getWaiting() != null;
    }

    private static boolean isWaitingInitializing(ContainerState state) {
        return state != null
                && state.getWaiting() != null
                && POD_INITIALIZING.equals(state.getWaiting().getReason());
    }

    private static boolean hasRunningContainer(Pod pod) {
        return getContainerStatus(pod).stream().anyMatch(KubernetesUtils::isRunning);
    }

    private static boolean isRunning(ContainerStatus status) {
        return status.getReady() != null
                && status.getState() != null
                && status.getState().getRunning() != null;
    }

    private static boolean hasCompletedContainer(Pod pod) {
        return getContainerStatus(pod).stream().anyMatch(KubernetesUtils::isCompleted);
    }

    private static boolean isCompleted(ContainerStatus status) {
        return status.getState() != null
                && status.getState().getTerminated() != null
                && CONTAINER_COMPLETED.equals(status.getState().getTerminated().getReason());
    }

    /**
     * Returns the container status for all containers of the given pod. Returns an empty list
     * if the pod has no status
     *
     * @param pod the pod to return the container status for
     * @return list of container status
     *
     * @see Pod#getStatus()
     * @see PodStatus#getContainerStatuses()
     */
    public static List<ContainerStatus> getContainerStatus(Pod pod) {
        if (pod == null || pod.getStatus() == null) {
            return Collections.emptyList();
        }
        return pod.getStatus().getContainerStatuses();
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PodInfo {
        private String podName;

        private String status;

        private int readyContainers;

        private int totalContainers;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class PodMetrics {
        private Integer cpuUsage;

        private Integer cpuLimit;

        private Integer cpuRequest;

        private Long memoryUsage;

        private Long memoryLimit;

        private Long memoryRequest;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class NodeMetrics {
        private Integer cpuCapacity;

        private Integer cpuAllocatable;

        private Long memoryCapacity;

        private Long memoryAllocatable;
    }
}
